import path from 'node:path';
import fs from 'fs';
import os from 'node:os';
import { QQVersionConfigType, QQLevel } from './types';
import { RequestUtil } from './request';

export async function solveProblem<T extends (...arg: any[]) => any> (func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> {
  return new Promise<ReturnType<T> | undefined>((resolve) => {
    try {
      const result = func(...args);
      resolve(result);
    } catch {
      resolve(undefined);
    }
  });
}

export async function solveAsyncProblem<T extends (...args: any[]) => Promise<any>> (func: T, ...args: Parameters<T>): Promise<Awaited<ReturnType<T>> | undefined> {
  return new Promise<Awaited<ReturnType<T>> | undefined>((resolve) => {
    func(...args).then((result) => {
      resolve(result);
    }).catch(() => {
      resolve(undefined);
    });
  });
}

export function sleep (ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export function PromiseTimer<T> (promise: Promise<T>, ms: number): Promise<T> {
  const timeoutPromise = new Promise<T>((_resolve, reject) =>
    setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms)
  );
  return Promise.race([promise, timeoutPromise]);
}

export async function runAllWithTimeout<T> (tasks: Promise<T>[], timeout: number): Promise<T[]> {
  const wrappedTasks = tasks.map((task) =>
    PromiseTimer(task, timeout).then(
      (result) => ({ status: 'fulfilled', value: result }),
      (error) => ({ status: 'rejected', reason: error })
    )
  );
  const results = await Promise.all(wrappedTasks);
  return results
    .filter((result) => result.status === 'fulfilled')
    .map((result) => (result as { status: 'fulfilled'; value: T; }).value);
}

export function isNull (value: any) {
  return value === undefined || value === null;
}

export function isNumeric (str: string) {
  return /^\d+$/.test(str);
}

export function truncateString (obj: any, maxLength = 500) {
  if (obj !== null && typeof obj === 'object') {
    Object.keys(obj).forEach((key) => {
      if (typeof obj[key] === 'string') {
        // 如果是字符串且超过指定长度，则截断
        if (obj[key].length > maxLength) {
          obj[key] = obj[key].substring(0, maxLength) + '...';
        }
      } else if (typeof obj[key] === 'object') {
        // 如果是对象或数组，则递归调用
        truncateString(obj[key], maxLength);
      }
    });
  }
  return obj;
}

export function isEqual (obj1: any, obj2: any) {
  if (obj1 === obj2) return true;
  if (obj1 == null || obj2 == null) return false;
  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2;

  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);

  if (keys1.length !== keys2.length) return false;

  for (const key of keys1) {
    if (!isEqual(obj1[key], obj2[key])) return false;
  }
  return true;
}

export function getDefaultQQVersionConfigInfo (): QQVersionConfigType {
  if (os.platform() === 'linux') {
    return {
      baseVersion: '3.2.12.28060',
      curVersion: '3.2.12.28060',
      prevVersion: '',
      onErrorVersions: [],
      buildId: '27254',
    };
  }
  if (os.platform() === 'darwin') {
    return {
      baseVersion: '6.9.53.28060',
      curVersion: '6.9.53.28060',
      prevVersion: '',
      onErrorVersions: [],
      buildId: '28060',
    };
  }
  return {
    baseVersion: '9.9.15-28131',
    curVersion: '9.9.15-28131',
    prevVersion: '',
    onErrorVersions: [],
    buildId: '28131',
  };
}

export function getQQPackageInfoPath (exePath: string = '', version?: string): string {
  if (process.env['NAPCAT_QQ_PACKAGE_INFO_PATH']) {
    return process.env['NAPCAT_QQ_PACKAGE_INFO_PATH'];
  }
  let packagePath;
  if (os.platform() === 'darwin') {
    packagePath = path.join(path.dirname(exePath), '..', 'Resources', 'app', 'package.json');
  } else if (os.platform() === 'linux') {
    packagePath = path.join(path.dirname(exePath), './resources/app/package.json');
  } else {
    packagePath = path.join(path.dirname(exePath), './versions/' + version + '/resources/app/package.json');
  }
  // 下面是老版本兼容 未来去掉
  if (!fs.existsSync(packagePath)) {
    packagePath = path.join(path.dirname(exePath), './resources/app/versions/' + version + '/package.json');
  }
  return packagePath;
}

export function getQQVersionConfigPath (exePath: string = ''): string | undefined {
  if (process.env['NAPCAT_QQ_VERSION_CONFIG_PATH']) {
    return process.env['NAPCAT_QQ_VERSION_CONFIG_PATH'];
  }
  let configVersionInfoPath;
  if (os.platform() === 'win32') {
    configVersionInfoPath = path.join(path.dirname(exePath), 'versions', 'config.json');
  } else if (os.platform() === 'darwin') {
    const userPath = os.homedir();
    const appDataPath = path.resolve(userPath, './Library/Application Support/QQ');
    configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
  } else {
    const userPath = os.homedir();
    const appDataPath = path.resolve(userPath, './.config/QQ');
    configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
  }
  if (typeof configVersionInfoPath !== 'string') {
    return undefined;
  }
  // 老版本兼容 未来去掉
  if (!fs.existsSync(configVersionInfoPath)) {
    configVersionInfoPath = path.join(path.dirname(exePath), './resources/app/versions/config.json');
  }
  if (!fs.existsSync(configVersionInfoPath)) {
    return undefined;
  }
  return configVersionInfoPath;
}

export function calcQQLevel (level?: QQLevel) {
  if (!level) return 0;
  // const { penguinNum, crownNum, sunNum, moonNum, starNum } = level;
  const { crownNum, sunNum, moonNum, starNum } = level;
  // 没补类型
  return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum;
}

export function stringifyWithBigInt (obj: any) {
  return JSON.stringify(obj, (_key, value) =>
    typeof value === 'bigint' ? value.toString() : value
  );
}

export function parseAppidFromMajor (nodeMajor: string): string | undefined {
  const hexSequence = 'A4 09 00 00 00 35';
  const sequenceBytes = Buffer.from(hexSequence.replace(/ /g, ''), 'hex');
  const filePath = path.resolve(nodeMajor);
  const fileContent = fs.readFileSync(filePath);

  let searchPosition = 0;
  while (true) {
    const index = fileContent.indexOf(sequenceBytes, searchPosition);
    if (index === -1) {
      break;
    }

    const start = index + sequenceBytes.length - 1;
    const end = fileContent.indexOf(0x00, start);
    if (end === -1) {
      break;
    }
    const content = fileContent.subarray(start, end);
    if (!content.every(byte => byte === 0x00)) {
      try {
        return content.toString('utf-8');
      } catch {
        break;
      }
    }

    searchPosition = end + 1;
  }

  return undefined;
}

const baseUrl = 'https://github.com/NapNeko/NapCatQQ.git/info/refs?service=git-upload-pack';
const urls = [
  'https://j.1win.ggff.net/' + baseUrl,
  'https://git.yylx.win/' + baseUrl,
  'https://ghfile.geekertao.top/' + baseUrl,
  'https://gh-proxy.net/' + baseUrl,
  'https://ghm.078465.xyz/' + baseUrl,
  'https://gitproxy.127731.xyz/' + baseUrl,
  'https://jiashu.1win.eu.org/' + baseUrl,
  baseUrl,
];

async function testUrl (url: string): Promise<boolean> {
  try {
    await PromiseTimer(RequestUtil.HttpGetText(url), 5000);
    return true;
  } catch {
    return false;
  }
}

async function findAvailableUrl (): Promise<string | null> {
  for (const url of urls) {
    if (await testUrl(url)) {
      return url;
    }
  }
  return null;
}

export async function getAllTags (): Promise<string[]> {
  const availableUrl = await findAvailableUrl();
  if (!availableUrl) {
    throw new Error('No available URL for fetching tags');
  }
  const raw = await RequestUtil.HttpGetText(availableUrl);
  return raw
    .split('\n')
    .map(line => {
      const match = line.match(/refs\/tags\/(.+)$/);
      return match ? match[1] : null;
    })
    .filter(tag => tag !== null && !tag!.endsWith('^{}')) as string[];
}


export async function getLatestTag (): Promise<string> {
  const tags = await getAllTags();

  tags.sort((a, b) => compareVersion(a, b));

  const latest = tags.at(-1);
  if (!latest) {
    throw new Error('No tags found');
  }
  // 去掉开头的 v
  return latest.replace(/^v/, '');
}


function compareVersion (a: string, b: string): number {
  const normalize = (v: string) =>
    v.replace(/^v/, '') // 去掉开头的 v
      .split('.')
      .map(n => parseInt(n) || 0);

  const pa = normalize(a);
  const pb = normalize(b);
  const len = Math.max(pa.length, pb.length);

  for (let i = 0; i < len; i++) {
    const na = pa[i] || 0;
    const nb = pb[i] || 0;
    if (na !== nb) return na - nb;
  }
  return 0;
}
